// Copyright 2011 Google Inc. All Rights Reserved.
//
// naclrun runs nacl modules with secured file access.
//
// The example of usage:
//
//     naclrun --sel_ldr=path/to/sel_ldr --glibc=path/to/glibc.zip --workdir=. -- ./nacl_module.nexe
// Runs nacl_module.nexe with read/write access to the current directory
// (plus r/o access to glibc runtime libraries).
//
// There's a way to expose more files or directories to the virtual file system visible by nacl module,
// see -filemap for the details.
package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"path"

	"naclrun.googlecode.com/git/pkg/naclrun"
)

const (
	LibraryDir = "/nacl64/lib"
	RunnableLd = "runnable-ld.so"
)

var (
	workDir = flag.String("work_dir", "", "Working directory that becomes the root of the virtualized file system for NaCl module. "+
		"NaCl module would be allowed to create files there.")
	glibc          = flag.String("glibc", "", "path/to/glibc.zip")
	selLdr         = flag.String("sel_ldr", "", "Path to sel_ldr (Native Client loader)")
	filemap        = flag.String("filemap", "", "(optional) File map with the files allowed to open. See naclrun/proto/path_proxy.proto for the details.")
	useEnvironment = flag.Bool("use_environment", false, "If set, naclrun will pass environment to NaCl module")
	tmpfs          = flag.String("tmpfs", "/dev/shm", "(optional) If set, naclrun will advise sel_ldr to use tmpfs with the specified location (useful, if /dev/shm is not present)")
)

func createFS(zip naclrun.FS) (res *naclrun.CompositeFS) {
//	protoRes, err := naclrun.FromTextProto(*filemap)
//	if err != nil {
//		log.Fatal("Can't decode the value of --filemap: ", err)
//	}
	res = naclrun.NewCompositeFS(
		zip,
//		naclrun.NewVirtualFS(protoRes, true),
	)

	if *workDir != "" {
		vroot := path.Clean(*workDir)
		fi, err := os.Stat(vroot)
		if err != nil {
			log.Fatalf("os.Stat failed for work_dir: %s, err: %v", vroot, err)
		}
		if !fi.IsDirectory() {
			log.Fatalf("--work_dir does not point to the directory. os.Stat: %v", fi)
		}
		res.Add(naclrun.NewVirtualFS(naclrun.NewVirtualRootResolver(vroot), false))
	}
	return res
}

func grabNaClArgs() (module string, args []string) {
	shouldGrab := false
	for _, arg := range os.Args {
		if shouldGrab {
			args = append(args, arg)
			continue
		}
		if arg == "--" {
			shouldGrab = true
		}
	}

	if len(args) == 0 || args[0] == "" {
		fmt.Fprintf(os.Stderr, "NaCl module not specified. Run naclrun -help to see the usage.\n")
		os.Exit(1)
	}
	return args[0], args[1:]
}

func printUsage() {
	fmt.Fprint(os.Stderr, `Usage:
naclrun [options] -- nacl_module [ arg1 arg2 ... argN ]
nacl_module is a path to the nacl module to run using the path inside virtual fs exposed to NaCl.

`)

	flag.PrintDefaults()
}

func check(name, val string) {
	if val == "" {
		fmt.Fprintf(os.Stderr, "ERROR: %s is a required parameter. Please, specify its value.\n\n", name)
		fmt.Fprintf(os.Stderr, "Run naclrun -help to see all the options.\n")
		os.Exit(1)
	}
}

func main() {
	flag.Usage = printUsage
	flag.Parse()

	module, args := grabNaClArgs()
	check("-glibc", *glibc)
	check("-sel_ldr", *selLdr)

	zip, err := naclrun.NewZipFS(*glibc, *tmpfs)
	if err != nil {
		log.Fatalf("Could not open zip archive with nacl glibc: %s, err: %v", *glibc, err)
	}
	defer zip.Close()
	proxy := createFS(zip)
	runnableLd, err := zip.Open(path.Join(LibraryDir, RunnableLd), os.O_RDONLY, 0)
	if err != nil {
		log.Fatalf("Could not extract runnable ld from glibc archive: %v", err)
	}
	defer runnableLd.Close()
	ldPath := fmt.Sprintf("/proc/%d/fd/%d", os.Getpid(), runnableLd.Fd())

	env := []string{}
	if *useEnvironment {
		env = os.Envs
	}

	p, err := naclrun.Start(&naclrun.Params{
		RunnableLd: ldPath,
		Module:     module,
		SelLdr:     *selLdr,
		Args:       args,
		Proxy:      proxy,
		Env:        env,
		Tmpfs:      *tmpfs,
	})

	if err != nil {
		log.Fatal("Could not start sel_ldr: ", err)
	}

	w, _ := p.Wait(0)
	os.Exit(w.WaitStatus.ExitStatus())
}
